#include "stdafx.h"
#include "CppUnitTest.h"
#include <vex/iterable/concept/range_concepts.h>
#include <vex/query/from.h>
#include <vex/query/where.h>
#include <vex/iterable/make_any_range.h>
#include <vex.tests/mstest/tests.range/tests.range.h>
#include <vex.tests/mstest/tests.range/range_archetype.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace tests { namespace query_where {

    using namespace boost::assign;

    TEST_CLASS(test_class) {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.query.where")
        END_TEST_CLASS_ATTRIBUTE();

        VEX_TESTS_TRACKED_MEM_CHECK();
    public:
        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        TEST_METHOD(where_adapter_can_be_constructed_from_range_and_predicate)
        {
            {
                typedef std::vector<int> container_t;
                auto p = [](int v) { return v % 2 == 0; }; 
                typedef decltype(vex::query::from(std::declval<container_t&>())) range_t;    

                typedef vex::query::adapter::detail::where_dispatch<decltype(p)>::predicate_type predicate_t;
                typedef vex::query::adapter::where_adapter<range_t, predicate_t> adapter_t;

                Assert::IsTrue(std::is_same<range_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto p = [](container_t::value_type const& v) { return v.first % 2 == 0; };
                typedef decltype(vex::query::from(std::declval<container_t&>())) range_t;

                typedef vex::query::adapter::detail::where_dispatch<decltype(p)>::predicate_type predicate_t; 
                typedef vex::query::adapter::where_adapter<range_t, predicate_t> adapter_t;

                Assert::IsTrue(std::is_same<range_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::vector<int> container_t;
                auto p = [](int v) { return v % 2 == 0; }; 
                typedef decltype(vex::query::from(std::declval<container_t&>() | boost::adaptors::filtered(p))) range_t;    
                typedef vex::query::adapter::detail::where_dispatch<decltype(p)>::predicate_type predicate_t;
                typedef vex::query::adapter::where_adapter<range_t, predicate_t> adapter_t;

                Assert::IsTrue(std::is_same<range_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto p = [](int v) { return v % 2 == 0; };
                typedef decltype(vex::query::from(
                    std::declval<container_t&>() 
                    | boost::adaptors::map_keys)) range_t;

                typedef vex::query::adapter::detail::where_dispatch<decltype(p)>::predicate_type predicate_t;
                typedef vex::query::adapter::where_adapter<range_t, predicate_t> adapter_t;

                Assert::IsTrue(std::is_same<range_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

#if _MSC_VER == 1800 && BOOST_VERSION == 105500
#pragma message(__FILE__ " boost::container::slist does not compile with boost 105500 and MSC 1800")
            Assert::Fail(wlog_message()
                << __FILE__
                << L" boost::container::slist does not compile with boost 105500 and MSC 1800"
                | as_string);
#else
            {
                typedef boost::container::slist<int> container_t;
                auto p = [](int v) { return v % 2 == 0; };
                typedef decltype(vex::query::from(std::declval<container_t&>())) range_t;

                typedef vex::query::adapter::detail::where_dispatch<decltype(p)>::predicate_type predicate_t;
                typedef vex::query::adapter::where_adapter<range_t, predicate_t> adapter_t;

                Assert::IsTrue(std::is_same<range_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::forward, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::ForwardRange<adapter_t>));
            }
#endif
        }

        TEST_METHOD(where_clause_from_stl_containers)
        {
            auto p = [](int v) { return v % 2 == 0; };

            {
                typedef std::vector<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::list<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::deque<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto map_p = [](container_t::value_type const & v) { return v.first % 2 == 0; };
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(map_p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
        }

        TEST_METHOD(where_clause_from_stl_iterator_ranges)
        {
            auto p = [](int v) { return v % 2 == 0; };

            {
                typedef std::vector<int> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t&>().begin(), 
                    std::declval<container_t&>().end())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::list<int> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t const&>().begin(), 
                    std::declval<container_t const&>().end())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::const_reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::deque<int> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t&>().begin(), 
                    std::declval<container_t&>().end())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto map_p = [](container_t::value_type const & v) { return v.first % 2 == 0; };
                typedef decltype(vex::query::from(
                    std::declval<container_t&>().begin(), 
                    std::declval<container_t&>().end())
                    | vex::query::where(map_p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
        }

        TEST_METHOD(where_clause_from_boost_container_vector)
        {
            auto p = [](int v) { return v % 2 == 0; };

#if _MSC_VER == 1800 && BOOST_VERSION == 105500
#pragma message(__FILE__ " boost containers do not compile with boost 105500 and MSC 1800")
            Assert::Fail(wlog_message()
                << __FILE__
                << L" boost::containers do not compile with boost 105500 and MSC 1800"
                | as_string);
#else
            {
                typedef boost::container::vector<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
#endif
        }

        TEST_METHOD(where_clause_from_boost_container_stable_vector)
        {
            auto p = [](int v) { return v % 2 == 0; };

#if _MSC_VER == 1800 && BOOST_VERSION == 105500
#pragma message(__FILE__ " boost containers do not compile with boost 105500 and MSC 1800")
            Assert::Fail(wlog_message()
                << __FILE__
                << L" boost::containers do not compile with boost 105500 and MSC 1800"
                | as_string);
#else
            {
                typedef boost::container::stable_vector<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
#endif

        }

        TEST_METHOD(where_clause_from_boost_container_slist)
        {
            auto p = [](int v) { return v % 2 == 0; };

#if _MSC_VER == 1800 && BOOST_VERSION == 105500
#pragma message(__FILE__ " boost containers do not compile with boost 105500 and MSC 1800")
            Assert::Fail(wlog_message()
                << __FILE__
                << L" boost::containers do not compile with boost 105500 and MSC 1800"
                | as_string);
#else
            {
                typedef boost::container::slist<int> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::forward, adapter_t::category>::value, wlog_message()
                    << "expected: " << std::endl
                    << typeid(vex::iterable::bidirectional).name() << std::endl
                    << "got: " << std::endl
                    << typeid(adapter_t::category).name() << std::endl | as_string);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::ForwardRange<adapter_t>));
            }
#endif
        }

        TEST_METHOD(where_clause_from_piped_boost_ranges)
        {
            auto p_even = [](int v) { return v % 2 == 0; };
            auto p_less_than_10 = [](int v) { return v < 10; };

            {
                typedef std::vector<int> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t&>() 
                    | boost::adaptors::filtered(p_even))
                    | vex::query::where(p_less_than_10)) adapter_t;
                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));

                {// really instantiate
                    container_t c;
                    c += 1, 2, 3, 4, 5, 11, 12, 13;
                    auto r = vex::query::from(c | boost::adaptors::filtered(p_even))
                        | vex::query::where(p_less_than_10);
                    Assert::IsTrue(r.move_front());
                    Assert::AreEqual(2, r.front());
                    Assert::AreEqual(4, r.back());
                }
            }

            {
                typedef std::list<int> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t&>() 
                    | boost::adaptors::filtered(p_even))
                    | vex::query::where(p_less_than_10)) adapter_t;
                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));

                {// really instantiate
                    container_t c;
                    c += 1, 2, 3, 4, 5, 11, 12, 13;
                    auto r = vex::query::from(c | boost::adaptors::filtered(p_even))
                        | vex::query::where(p_less_than_10);
                    Assert::IsTrue(r.move_front());
                    Assert::AreEqual(2, r.front());
                    Assert::AreEqual(4, r.back());
                }
            }
        }

        TEST_METHOD(where_clause_from_piped_boost_ranges_from_associative_containers)
        {
            auto p_even = [](int v) { return v % 2 == 0; };
            auto p_less_than_10 = [](int v) { return v < 10; };
            {
                typedef std::map<int, std::string> container_t;
                auto p_m_even = [](container_t::value_type const& v) { return v.first % 2 == 0; };
                auto p_m_less_than_10 = [](container_t::value_type const& v) { return v.first < 10; };

                typedef decltype(vex::query::from(
                    std::declval<container_t&>() 
                    | boost::adaptors::filtered(p_m_even))
                    | vex::query::where(p_m_less_than_10)) adapter_t;
                Assert::IsTrue(std::is_same<container_t::reference, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));

                {// really instantiate
                    container_t c;
                    insert(c)(1, "1")(2, "2")(4, "4")(11, "11");
                    auto r = vex::query::from(c | boost::adaptors::filtered(p_m_even))
                        | vex::query::where(p_m_less_than_10);
                    Assert::IsTrue(r.move_front());
                    Assert::AreEqual(2, r.front().first);
                    Assert::AreEqual(4, r.back().first);
                }
            }
        }

        TEST_METHOD(where_clause_from_piped_boost_ranges_using_map_keys_adaptor)
        {
            auto p_even = [](int v) { return v % 2 == 0; };
            auto p_less_than_10 = [](int v) { return v < 10; };

            { // step one, just make a range
                typedef std::map<int, std::string> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>() | 
                    boost::adaptors::map_keys)) range_t;
                Assert::IsTrue(std::is_same<int const&, range_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional_range_tag, range_t::category>::value);
                {
                    // really instantiate
                    container_t c;
                    insert(c)(1, "1")(2, "2")(4, "4")(11, "11");
                    auto r = vex::query::from(c | boost::adaptors::map_keys);
                    Assert::IsTrue(r.move_front());
                    Assert::AreEqual(1, r.front());
                    Assert::AreEqual(11, r.back());
                }
            }

            { // step two, filter range
                typedef std::map<int, std::string> container_t;
                typedef decltype(vex::query::from(
                    std::declval<container_t&>() 
                    | boost::adaptors::map_keys
                    | boost::adaptors::filtered(p_even))) range_t;

                Assert::IsTrue(std::is_same<int const&, range_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional_range_tag, range_t::category>::value);
                {
                    // really instantiate
                    container_t c;
                    insert(c)(1, "1")(2, "2")(4, "4")(11, "11");
                    auto r = vex::query::from(c 
                        | boost::adaptors::map_keys
                        | boost::adaptors::filtered(p_even));
                    Assert::IsTrue(r.move_front());
                    Assert::AreEqual(2, r.front());
                    Assert::AreEqual(4, r.back());
                }
            }
        }

        TEST_METHOD(where_clause_from_piped_boost_ranges_using_map_keys_adaptor_failing)
        {
            auto p_even = [](int v) { return v % 2 == 0; };
            auto p_less_than_10 = [](int v) { return v < 10; };
            { // step three, apply vex::where filter to map_keyed...
                typedef std::map<int, std::string> container_t;
                typedef decltype(vex::query::from(std::declval<container_t&>() | boost::adaptors::map_keys))
                    input_range_t;

                Logger::WriteMessage(typeid(input_range_t).name());

                Assert::IsTrue(vex::iterable::is_range<input_range_t>::value);

                typedef decltype(std::declval<input_range_t const&>() | vex::query::where(p_even))
                    where_range_t;

                Logger::WriteMessage(typeid(where_range_t).name());

                Assert::IsTrue(vex::iterable::is_range<where_range_t>::value);

                typedef decltype(vex::query::from(boost::adaptors::keys(std::declval<container_t const&>()))
                    //std::declval<container_t&>() | boost::adaptors::map_keys)
                    | vex::query::where(p_even)) range_t;

                Assert::IsTrue(std::is_same<int const&, range_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional_range_tag, range_t::category>::value);
            }
        }
    };
}}